/**
 * Filename: tell_wait.c
 * Author:   csbonkers
 * Contact:  csbonkers@mail.ynu.edu.cn
 * Date:     4/30/21
 * Description:
 *    用信号实现父子进程之间的同步，这是信号应用的另一个实例
 *    TELLWAIT、WAIT_PARENT、TELL_PARENT、WAIT_CHILD、TELL_CHILD
 */
#include "apue.h"
#include <signal.h>

// set non-zero by sig handler, parent wait on sigflag_p, child wait on sigflag_c;
static volatile sig_atomic_t sigflag_p = 0,sigflag_c = 0;
static sigset_t newmask= {}, oldmask = {}, zeromask = {};

// 修改子进程的标志
static void sig_usr1(int signo) { // one signal handler for SIGUSR1
  sigflag_c = 1;
}

static void sig_usr2(int signo) { // one signal handler for SIGUSR1 and SIGUSR2
  sigflag_p = 1;
}

// 建立对SIGUSR1和SIGUSR2d的信号处理和信号屏蔽字
void TELLWAIT() {
  if (c_signal(SIGUSR1, sig_usr1) == SIG_ERR) {
    err_sys("signal(SIGUSR1) error");
  }
  if (c_signal(SIGUSR2, sig_usr2) == SIG_ERR) {
    err_sys("signal(SIGUSR2) error");
  }
  sigemptyset(&zeromask);
  sigemptyset(&newmask);
  sigaddset(&newmask, SIGUSR1);
  sigaddset(&newmask, SIGUSR2);
  // block SIGUSR1/SIGUSR2, and save current signal mask
  if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) {
    err_sys("SIG_BLOCK error");
  }
}

// SIGUSR2 信号由子进程发送给父进程
void TELL_PARENT(pid_t pid) {
  kill(pid, SIGUSR2); // tell parent we're done
}

// called by child, wait until signal SIGUSR1 generated by parent
void WAIT_PARENT() {
  while (sigflag_c == 0) {
    sigsuspend(&zeromask); // wait for parent
  }
  sigflag_c = 0;
  // reset signal mask to original value
  if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) {
    err_sys("SIG_SETMASK error");
  }
}

// SIGUSR1信号 由父进程发送给子进程
void TELL_CHILD(pid_t pid) {
  kill(pid, SIGUSR1); // tell child we're done
}

void WAIT_CHILD() {
  while (sigflag_p == 0) {
    sigsuspend(&zeromask); // wait for child
  }
  sigflag_p = 0;
  // reset signal mask to  original value
  if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) {
    err_sys("SIG_SETMASK error");
  }
}

/*
 * TELL_WAIT (setup signal mask and signal handler)
 *
 * 1. parent goes first
 *         main
 *          |
 *          |tell_wait(setup)
 *          |
 *          |P(fork)
 *          |
 *         / \
 *        /   \
 *       /     \
 *     P         C
 *    ---       ---
 *     |         |
 *     |     wait_parent (blocking)
 *     |        ...
 *     |
 * :do sth:  ->(wake up)
 *     |    /    |
 *     |   /     |
 *tell_child     |
 *     |         |
 *     |         |
 *     .         |
 *     .         .
 *
 *
 * 2. child goes first
 *
 *         main
 *           |
 *           |tell_wait(setup)
 *           |
 *           |P(fork)
 *           |
 *          / \
 *         /   \
 *        /     \
 *      P         C
 *      |         |
 *  wait_child    |
 *  (blocking)  (do sth)
 *     ..         |
 * (wakeup)<-     |
 *      |    \    |
 *     con    \   |
 *      |      \  |
 *      |       tell_parent
 *      |         |
 *      |         |
 *      .         .
 */